/*
MIT License

Copyright (c) 2022 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "ScriptAnalyzer.h"
#include "simodo/interpret/AnalyzeException.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/inout/format/fmt.h"

#include "simodo/interpret/StackOfNames.h"

#include <cassert>

namespace simodo::interpret
{
    namespace 
    {
        SemanticDataCollector_null null_collector;
    }

    ScriptAnalyzer::ScriptAnalyzer(ModuleManagement_interface & module_management)
        : ScriptSemantics_abstract(module_management)
        , _collector(null_collector)
    {
    }

    ScriptAnalyzer::ScriptAnalyzer(ModuleManagement_interface & module_management, SemanticDataCollector_interface & collector)
        : ScriptSemantics_abstract(module_management)
        , _collector(collector)
    {
    }

    bool ScriptAnalyzer::checkInterpretType(InterpretType interpret_type) const
    {
        return interpret_type == InterpretType::Analyzer;
    }

    InterpretState ScriptAnalyzer::performOperation(const ast::Node & op)
    {
        try
        {
            return ScriptSemantics_abstract::performOperation(op);
        }
        catch(const AnalyzeException & e)
        {
            inter().reporter().reportError(e.location(), e.what());
            _number_of_mistakes ++;
        }

        if (_number_of_mistakes >= MAX_NUMBER_OF_MISTAKES) 
            throw AnalyzeException("ScriptAnalyzer::performOperation", 
                                    op.token().makeLocation(inter().files()), 
                                    inout::fmt("The number of errors has exceeded the allowable limit"));
        
        return InterpretState::Flow;
    }

    InterpretState ScriptAnalyzer::before_start()
    {
        return ScriptSemantics_abstract::before_start();
    }

    InterpretState ScriptAnalyzer::before_finish(InterpretState state)
    {
        return ScriptSemantics_abstract::before_finish(state);
    }

    // InterpretState ScriptAnalyzer::executePushVariable(const inout::Token & variable_name)
    // {
    //     InterpretState state           = ScriptSemantics_abstract::executePushVariable(variable_name);
    //     const variable::Variable & var = inter().table().variable(inter().table().top());

    //     if (!var.origin().name().isError())
    //         _collector.collectNameUsed(var.origin(), variable_name.location());

    //     return state;
    // }

    // InterpretState ScriptAnalyzer::executeObjectElement(const ast::Node & op)
    // {

    // }

    // InterpretState ScriptAnalyzer::executeFunctionCall(const ast::Node & op)
    // {
    //     return ScriptSemantics_abstract::executeFunctionCall(op);
    // }

    InterpretState ScriptAnalyzer::executePrint(bool /*need_detailed_report*/)
    {
        inter().stack().pop();

        return InterpretState::Flow;
    }

    InterpretState ScriptAnalyzer::executeBlock(bool is_beginning_of_block, const ast::Node & op)
    {
        if (is_beginning_of_block)
            ;
            // _collector.collectFrameBegin(op.operation_symbol().location, FrameType::CodeBlock);
        else {
            /// \todo Добавить сбор информации о локальных переменных

            // _collector.collectFrameEnd(op.operation_symbol().location);
        }

        return ScriptSemantics_abstract::executeBlock(is_beginning_of_block, op);
    }

    InterpretState ScriptAnalyzer::executeImport(const inout::Token & path, const ast::Node & params)
    {
        InterpretState state = ScriptSemantics_abstract::executeImport(path, params);

        notifyDeclared(inter().stack().variable(inter().stack().top()));

        return state;
    }

    InterpretState ScriptAnalyzer::executeContract(const ast::Node & op)
    {
        InterpretState state = ScriptSemantics_abstract::executeContract(op);

        notifyDeclared(inter().stack().variable(inter().stack().top()));

        return state;
    }

    InterpretState ScriptAnalyzer::executeDeclaration(const ast::Node & op)
    {
        InterpretState state = ScriptSemantics_abstract::executeDeclaration(op);

        // notifyDeclared(inter().table().variable(inter().table().top()));

        // assert(stack().index_over_top() > 0);

        // const variable::Variable & v = stack().variable(stack().top());
        // _declared.back() = v.name();

        return state;
    }

    InterpretState ScriptAnalyzer::executeDeclarationCompletion()
    {
        InterpretState state = ScriptSemantics_abstract::executeDeclarationCompletion();

        return state;
    }

    InterpretState ScriptAnalyzer::executeFunctionDefinitionEnd(const ast::Node & op)
    {
        name_index_t               function_index    = stack().top();
        const variable::Variable & function_variable = stack().variable(function_index).origin();

        if (function_variable.type() == variable::ValueType::Null)
            return InterpretState::Flow;

        assert(function_variable.type() == variable::ValueType::Function);

        const std::shared_ptr<variable::Object> function_object = function_variable.value().getObject();
        assert(function_object->variables().size() >= 2);

        _functions_for_analyze.push_back(function_variable);
        
        return ScriptSemantics_abstract::executeFunctionDefinitionEnd(op);
    }

    InterpretState ScriptAnalyzer::executePostAssignment(const ast::Node & op)
    {
        InterpretState state = ScriptSemantics_abstract::executePostAssignment(op);

        // checkFunctions();

        return state;
    }

    InterpretState ScriptAnalyzer::executeCheckState(const ast::Node & op)
    {
        checkFunctions();

        return ScriptSemantics_abstract::executeCheckState(op);
    }

    // InterpretState ScriptAnalyzer::executeAssignment(const ast::Node & op)
    // {
    //     InterpretState state = ScriptSemantics_abstract::executeAssignment(op);

    //     name_index_t               var_index = stack().top();
    //     const variable::Variable & var       = stack().variable(var_index).origin();

    //     if (var.origin().value().isFunction()) {
    //         eraseFunction(var.origin());
    //         _functions_for_analyze.push_back(var.origin());
    //     }

    //     return state;
    // }

    InterpretState ScriptAnalyzer::executeConditional(ScriptOperationCode code, const ast::Node & op_true, const ast::Node * op_false)
    {
        name_index_t       condition_index    = stack().top();
        variable::Variable condition_variable = stack().variable(condition_index).origin();

        try
        {
            if (condition_variable.type() != variable::ValueType::Bool
             && !condition_variable.value().isError()) {
                condition_variable = inter().expr().convertVariable(condition_variable, variable::ValueType::Bool);

                if (condition_variable.value().isError())
                    return InterpretState::Flow;
            }

            inter().stack().pop();

            inter().addFlowLayer(op_true, 
                    [this, op_false](const FlowLayerInfo &) 
                    {
                        if (op_false)
                            inter().addFlowLayer(*op_false);
                    });

            return InterpretState::Flow;
        }
        catch(...)
        {
            if (code == ScriptOperationCode::Ternary)
                inter().stack().pop(inter().stack().top() - condition_index);

            throw;
        }

        return InterpretState::Flow;
    }

    void ScriptAnalyzer::notifyDeclared(const variable::Variable & var)
    {
        _collector.collectNameDeclared(var);
    }

    void ScriptAnalyzer::notifyInitiated(const variable::Variable & var)
    {
        _collector.collectNameInitiated(var);

        if (var.origin().value().isFunction()) {
            eraseFunction(var.origin());
            _functions_for_analyze.push_back(var.origin());
        }
    }

    void ScriptAnalyzer::notifyNameUsed(const variable::Variable & variable, const inout::TokenLocation & location)
    {
        _collector.collectNameUsed(variable, location);
    }

    void ScriptAnalyzer::notifyRef(const inout::Token & token, const std::u16string & ref)
    {
        _collector.collectRef(token, ref);
    }

    void ScriptAnalyzer::notifyBeforeFunctionCalling(const variable::Variable & function)
    {
        eraseFunction(function);
    }

    void ScriptAnalyzer::notifyRemoteFunctionLaunch(const inout::TokenLocation & location)
    {
        _collector.collectRemoteFunctionLaunch(location);
    }

    void ScriptAnalyzer::checkFunctions()
    {
        while(!_functions_for_analyze.empty()) {
            const variable::Variable function_variable = _functions_for_analyze.back();
            _functions_for_analyze.pop_back();

            assert(function_variable.type() == variable::ValueType::Function);

            const std::shared_ptr<variable::Object> function_object = function_variable.value().getObject();
            assert(function_object->variables().size() >= 2);

            stack().push(function_variable);

            const variable::InternalFunction & 
                             internal_function = std::get<variable::InternalFunction>(function_object->variables()[0].value().variant());
            boundary_index_t boundary_index    = stack().startBoundary(BoundScope::Local, BOUNDARY_MARK_FUNCTION_FRAME);

            for(size_t i=2; i < function_object->variables().size(); ++i) {
                const variable::Variable & v = function_object->variables()[i];
                inter().stack().push(v);
            }

            const variable::Value & tethered_value = function_variable.origin().spec().object()->find(variable::SPEC_TETHERED);

            if (tethered_value.isBool() && tethered_value.getBool()) {
                boundary_index_t module_bound_index = stack().findNearestMarkedBoundary(BOUNDARY_MARK_MODULE_FRAME, BoundScope::Global);
                assert(module_bound_index != UNDEFINED_BOUNDARY_INDEX);  
                const auto [begin,end] = stack().boundaryLowAndTop(module_bound_index);
                for(name_index_t i = begin; i < end; ++i)
                    stack().pushRef(stack().variable(i), function_variable.location());
            }
            else
                for(const variable::Variable & closure : internal_function.closures.variables())
                    if (closure.name() == SELF_VARIABLE_NAME) {
                        variable::Variable self = makeSelf(function_variable.origin(), closure.location());
                        inter().stack().push(self);
                        notifyDeclared(self);
                    }
                    else {
                        inter().stack().push(closure);
                        notifyDeclared(closure);
                    }

            variable::Variable   declared_return_variable = function_object->variables()[1];
            variable::ValueType  declared_return_type     = declared_return_variable.type();
            inout::TokenLocation function_location        = declared_return_variable.location();

            inter().stack().setBoundaryToGlobal(boundary_index);
            inter().expr().setReturnValue({variable::ValueType::Null});
            inter().addFlowLayer(
                    *internal_function.code, 
                    boundary_index,
                    [this, boundary_index, declared_return_type, function_location](const FlowLayerInfo & flow) 
                    {
                        inter().stack().clearBoundary(boundary_index);
                        inter().stack().pop(); 

                        if (flow.error_sign) 
                            return;

                        if (declared_return_type != variable::ValueType::Null
                         && inter().expr().return_value().isNull() && !inter().expr().return_value().isError())
                            throw AnalyzeException("ScriptSemantics_abstract::executeReturn", 
                                                    function_location.makeLocation(inter().files()), 
                                                    inout::fmt("The function must return a value"));
                    });
        }
    }

    void ScriptAnalyzer::eraseFunction(const variable::Variable & function)
    {
        if (!function.value().isFunction())
            return;

        auto it = std::find_if(_functions_for_analyze.begin(), _functions_for_analyze.end(),
                            [function](const variable::Variable & f)
                            {
                                return f.value().isFunction() && f.value().getFunction().get() == function.value().getFunction().get();
                            });

        if (it != _functions_for_analyze.end())
            _functions_for_analyze.erase(it);
    }

    variable::Variable ScriptAnalyzer::makeSelf(const variable::Variable & function_origin, const inout::TokenLocation & location)
    {
        if (function_origin.type() == variable::ValueType::Function) {
            std::shared_ptr<variable::Object> function_object  = function_origin.value().getObject();

            if (!function_object->variables().empty()
             && function_object->variables()[0].type() == variable::ValueType::IntFunction) {
                std::shared_ptr<variable::Object> self = function_object->variables()[0].value().getIntFunctionParent();

                if (self)
                    return {SELF_VARIABLE_NAME, self, location, {{{variable::SPEC_ORIGIN, variable::SPEC_ORIGIN_MODULE}}}};
            }
        }

        std::shared_ptr<variable::Object> self = std::make_shared<variable::Object>();

        boundary_index_t module_bound = stack().findNearestMarkedBoundary(BOUNDARY_MARK_MODULE_FRAME, BoundScope::Global);
        if (module_bound == UNDEFINED_BOUNDARY_INDEX)
            return variable::error_variable(location);
        
        auto [begin_index, end_index] = stack().boundaryLowAndTop(module_bound);

        for(name_index_t i = begin_index; i < end_index; ++i) {
            variable::Variable & v = stack().variable(i);
            self->variables().push_back(v);
        }

        return {SELF_VARIABLE_NAME, self, location, {{{variable::SPEC_ORIGIN, variable::SPEC_ORIGIN_MODULE}}}};
    }

}